package com.jcsz.gulimall.cart.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.jcsz.common.constant.CartConstant;
import com.jcsz.common.utils.R;
import com.jcsz.gulimall.cart.feign.ProductFeignService;
import com.jcsz.gulimall.cart.interceptor.CartInterceptor;
import com.jcsz.gulimall.cart.service.CartService;
import com.jcsz.gulimall.cart.to.UserInfoTo;
import com.jcsz.gulimall.cart.vo.Cart;
import com.jcsz.gulimall.cart.vo.CartItem;
import com.jcsz.gulimall.cart.vo.SkuInfoVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @className: CartServiceImpl
 * @author: 一把Po吉他
 * @date: 2022/11/10 12:05
 * @version: 1.0
 **/
@Service
public class CartServiceImpl implements CartService {
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    ThreadPoolExecutor executor;

    @Override
    public Cart getCart() throws ExecutionException, InterruptedException {
        Cart cart = new Cart();

        // 1、绑定hash操作
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        if (userInfoTo.getUserId() != null) {
            if (!StringUtils.isEmpty(userInfoTo.getUserKey())) {
                List<CartItem> cartItems = getCartItems(CartConstant.CART_PREFIX + userInfoTo.getUserKey());
                if (cartItems.size() > 0 && cartItems != null) {
                    for (CartItem cartItem : cartItems) {
                        addToCart(cartItem.getSkuId(), cartItem.getCount());
                    }
                    redisTemplate.delete(CartConstant.CART_PREFIX + userInfoTo.getUserKey());
                }

            }
            List<CartItem> cartItems = getCartItems(CartConstant.CART_PREFIX + userInfoTo.getUserId());
            cart.setItems(cartItems);
            return cart;
        } else {
            cart.setItems(getCartItems(CartConstant.CART_PREFIX + userInfoTo.getUserKey()));
            return cart;
        }
    }

    private List<CartItem> getCartItems(String key) {
        BoundHashOperations<String, Object, Object> cartOps = redisTemplate.boundHashOps(key);
        List<Object> values = cartOps.values();
        List<CartItem> collect = values.stream().map((obj) -> {
            String s = (String) obj;
            CartItem cartItem = JSON.parseObject(s, CartItem.class);
            return cartItem;
        }).collect(Collectors.toList());
        return collect;
    }

    @Override
    public void addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
        // 获取到要操作的购物车
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        // 1、如果购物车中已有该商品那么需要做的就是修改商品的数量，如果没有该商品需要进行添加该商品到购物车中
        String result = (String) cartOps.get(skuId.toString());// 商品id作为键，先去Redis里面查看有没有这个相同的商品
        if (StringUtils.isEmpty(result)) {// 如果返回为空，那么说明购物车中没有这个商品，那就要执行添加
            // 2、添加商品到购物车
            // 第一个异步任务,查询当前商品信息
            CartItem cartItem = new CartItem();
            CompletableFuture<Void> skuInfoFutare = CompletableFuture.runAsync(() -> {
                // 2.1、远程查询出当前要添加的商品信息
                // 添加哪个商品到购物车，先查询到这个商品的信息
                R r = productFeignService.getSkuInfo(skuId);
                // 获取远程返回的数据，远程数据都封装在skuInfo中
                SkuInfoVo skuInfo = r.get("skuInfo", new TypeReference<SkuInfoVo>() {
                });
                cartItem.setChecked(true);// 添加商品到购物车，那么这个商品一定是选中的
                cartItem.setSkuId(skuId);// 查询的是哪个商品的id，那么这个商品id就是哪个
                cartItem.setCount(num);// 当前商品添加的数量
                cartItem.setPrice(skuInfo.getPrice());// 当前商品的价格
                cartItem.setTitle(skuInfo.getSkuTitle());// 当前商品的标题
                cartItem.setImage(skuInfo.getSkuDefaultImg());// 当前商品的图片信息

            }, executor);
            // 3、第二个异步任务，远程查询sku组合信息
            CompletableFuture<Void> attrFutare = CompletableFuture.runAsync(() -> {
                List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);// 根据skuid来查
                cartItem.setSkuAttr(skuSaleAttrValues);// 远程查询出来到sku组合信息，需要在购物车中进行显示
            }, executor);
            // 4、两个异步任务都完成，才能把数据放到redis中
            CompletableFuture.allOf(skuInfoFutare, attrFutare).get();
            String jsonString = JSON.toJSONString(cartItem);// 添加商品到购物车中
            // 把购物项的数据保存redis中
            cartOps.put(skuId.toString(), jsonString);
        } else {
            // 如果购物车中有这个商品，那么需要做的就是将商品的数量进行更改,也即是新添加的商品数量加上当前购物车中商品数量
            CartItem cartItem = JSON.parseObject(result, CartItem.class);
            cartItem.setCount(num + cartItem.getCount());
            String newString = JSON.toJSONString(cartItem);
            // 更新redis
            cartOps.put(skuId.toString(), newString);
        }
    }

    @Override
    public CartItem getCartItem(Long skuId) {
        // 1、绑定hash操作
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        // 2、从redis中拿到值
        String resultString = (String) cartOps.get(skuId.toString());
        // 3、转成对象
        CartItem cartItem = JSON.parseObject(resultString, CartItem.class);
        // 4、返回
        return cartItem;
    }

    @Override
    public void checkItem(Long skuId, Integer checked) {
//        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
//        String stringJson = (String) cartOps.get(skuId.toString());
//        CartItem cartItem = JSON.parseObject(stringJson, CartItem.class);
        CartItem cartItem = getCartItem(skuId);
        cartItem.setChecked(checked == 1 ? true : false);
        String jsonString = JSON.toJSONString(cartItem);
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        cartOps.put(skuId.toString(), jsonString);
    }

    @Override
    public void countItem(Long skuId, Integer num) {
        CartItem cartItem = getCartItem(skuId);
        cartItem.setCount(num);
        String jsonString = JSON.toJSONString(cartItem);
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        cartOps.put(skuId.toString(), jsonString);
    }

    @Override
    public void deleteItem(Long skuId) {
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        cartOps.delete(skuId.toString());
    }

    private BoundHashOperations<String, Object, Object> getCartOps() {
        // 要想知道用户登录了还是没有登录就进行添加购物车，请求发送给controller的时候，拦截器就开始执行了，拦截器中会拿到用户的信息
        // 在这里得到用户登录的信息，只要是同一次请求，在任何位置都能拿到
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();

        String cartKey = "";
        // 临时用户按照userkey进行绑定
        if (userInfoTo.getUserId() != null) {
            // 如果userInfo已经登录了，肯定是给他登录后的购物车添加商品，只有没登录，那么所有的操作才在临时购物车里面
            cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserId();
        } else {
            cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
        }
        /**
         * 将一个商品，添加到对应的购物车
         * 1、添加到购物车，其实有两个操作
         *      1、购物车里面之前就有了这个商品，相当于redis里面已经有了这个商品，就只需要把redis里面的商品的数量进行更改
         *      2、购物车里面没有这个商品，那么就需要给redis里面新增这个商品的信息
         */
        // 绑定了hash操作，以后所有对redis的增删改查，都是针对这个key的增删改查，也就是针对这个购物车进行操作
        return redisTemplate.boundHashOps(cartKey);
    }

    @Override
    public List<CartItem> getCurrentUserCartItem() {
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        if (userInfoTo.getUserId() != null) {
            List<CartItem> cartItems = getCartItems(CartConstant.CART_PREFIX + userInfoTo.getUserId());
            if (cartItems.size() > 0 && cartItems != null) {
                List<CartItem> collect = cartItems.stream().filter(item ->
                        item.getChecked()
                ).map((item) -> {
                    BigDecimal price = productFeignService.getPrice(item.getSkuId());
                    item.setPrice(price);
                    return item;
                }).collect(Collectors.toList());
                return collect;
            }
        }
        return null;
    }
}
